#if SILVERLIGHT
using System.Collections.ObjectModel;
using System.Windows.Browser;
using System.Windows.Controls;
#endif

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.ServiceModel.DomainServices.Client;
#if SILVERLIGHT
using AbstraX.ClientInterfaces;
using SilverlightDatabase;
using DanielVaughan.Logging;
#else
using AbstraX.ServerInterfaces;
using System.Web.UI;
using log4net;
#endif
using System.Threading;
using Microsoft.Practices.Unity;
using AbstraX.Contracts;
using AbstraX.Services;
using AbstraX.ViewModels;
using AbstraX;
using System.ServiceModel;
using System.Net;
using System.Windows;
using System.Windows.Threading;
using System.ComponentModel;
using AbstraX.ViewModels.ModelTree;
using AbstraX.ViewModels.BindingsTree;
using System.Diagnostics;
using System.ServiceModel.Dispatcher;
using AbstraX.BuildWizard;
using BuildInvokeOperation = AbstraX.BuildWizard.BuildInvokeOperation;
using AbstraX.BuildEvents;
using System.IO;
using Utils;
using System.IO.IsolatedStorage;
using System.Xml.Serialization;
using System.Windows.Media;
using System.Text.RegularExpressions;
using System.Threading.Tasks;

#if SILVERLIGHT
using SilverlightDatabase.Core;
using SilverlightDatabase.Exceptions;
using Hydra.Shell.Interfaces;
#endif

namespace AbstraX
{
    public delegate void OnSolutionOpenedHandler(string solutionName);
    public delegate void OnShowUIHandler(string uiComponentURL, string parms);
    public delegate void OnGeneralMessageHandler(string message);
    public delegate void OnOutputMessageHandler(Color color, string message);
    public delegate void OnInternalErrorHandler(Exception exception);
    public delegate void OnInternalMessageHandler(string message);
    public delegate void OnGenerationCompletedHandler();
    public delegate void OnGeneralErrorHandler(RemotableException exception);
    public delegate void OnFatalErrorHandler(RemotableException exception);
    public delegate void OnPercentCompleteHandler(float percentComplete);
    public delegate void OnBuildFinishedHandler(BuildFinishedEventArgs e);
    public delegate void OnBuildStartedHandler(BuildStartedEventArgs e);
    public delegate void OnCustomEventRaisedHandler(CustomBuildEventArgs e);
    public delegate void OnErrorRaisedHandler(BuildErrorEventArgs e);
    public delegate void OnMessageRaisedHandler(BuildMessageEventArgs e);
    public delegate void OnProjectFinishedHandler(ProjectFinishedEventArgs e);
    public delegate void OnProjectStartedHandler(ProjectStartedEventArgs e);
    public delegate void OnStatusEventRaisedHandler(BuildStatusEventArgs e);
    public delegate void OnTargetFinishedHandler(TargetFinishedEventArgs e);
    public delegate void OnTargetStartedHandler(TargetStartedEventArgs e);
    public delegate void OnTaskFinishedHandler(TaskFinishedEventArgs e);
    public delegate void OnTaskStartedHandler(TaskStartedEventArgs e);
    public delegate void OnWarningRaisedHandler(BuildWarningEventArgs e);
    public delegate void OnBuildRequestedHandler(string generatorName);
    public delegate void OnBuildRequestCompletedHandler(string generatorName);

    public class AbstraXUIThread
    {
        public Dispatcher Dispatcher { get; set; }
        public void Run(Action a)
        {
            Dispatcher.BeginInvoke(a);
        }
    }

    public delegate void OnRootsLoadedHandler(object sender, EventArgs<IRootsCollection> args);

    public static class ProviderClientManager
    {
        public const string DATABASE_NAME = "AbstraXData";
        private static RootsCollection roots;
        private static List<ProviderListItem> providers;
        private static Dictionary<string, DomainContext> contexts;
        private static bool loading;
        private static Queue<ProviderListItem> providerQueue;
        private static BuildInvokeOperation buildInvokeOperation;
        private static EventsStartOperation eventsStartOperation;
        private static Dictionary<LoadOperation, Assembly> loadRootMapping;
        public static bool RootsLoaded { get; set; }
        public static event OnRootsLoadedHandler OnRootsLoaded;
        public static event OnShowUIHandler OnShowUI;
        public static event OnGeneralMessageHandler OnGeneralMessage;
        public static event OnPercentCompleteHandler OnPercentComplete;
        public static event OnGenerationCompletedHandler OnGenerationCompleted;
        public static event OnGeneralErrorHandler OnGeneralError;
        public static event OnFatalErrorHandler OnFatalError;
        public static event OnBuildFinishedHandler OnBuildFinished;
        public static event OnBuildStartedHandler OnBuildStarted;
        public static event OnCustomEventRaisedHandler OnCustomEventRaised;
        public static event OnErrorRaisedHandler OnErrorRaised;
        public static event OnMessageRaisedHandler OnMessageRaised;
        public static event OnProjectFinishedHandler OnProjectFinished;
        public static event OnProjectStartedHandler OnProjectStarted;
        public static event OnStatusEventRaisedHandler OnStatusEventRaised;
        public static event OnTargetFinishedHandler OnTargetFinished;
        public static event OnTargetStartedHandler OnTargetStarted;
        public static event OnTaskFinishedHandler OnTaskFinished;
        public static event OnTaskStartedHandler OnTaskStarted;
        public static event OnWarningRaisedHandler OnWarningRaised;
        public static event OnBuildRequestedHandler OnBuildRequested;
        public static event OnBuildRequestCompletedHandler OnBuildRequestCompleted;
        public static event EventHandler DataAvailable;
        public static event OnOutputMessageHandler OnOutputMessage;
        public static event OnInternalErrorHandler OnInternalError;
        public static event OnInternalMessageHandler OnInternalMessage;
        public static event OnSolutionOpenedHandler OnSolutionOpened;
        public static bool EnableCaching { get; set; }
        public static ILog Log { get; set; }
        public static Dictionary<string, Thread> namedThreads = new Dictionary<string, Thread>();

        public enum ImageDownloadType
        {
            Url,
            ItemType,
            Folder
        }

        public class LocalImage
        {
            public ImageDownloadType DownloadType { get; set; }
        }

        public class ProviderListItemLocal
        {
            public string FileName { get; set; }
            public DateTime ModifiedDate { get; set; }
            public string ID { get; set; }
        }

        [XmlInclude(typeof(ProviderListItemLocal)), XmlInclude(typeof(LocalImage))]
        public class SavedFile<TExtra>
        {
            public string FileName { get; set; }
            public byte[] Data { get; set; }
            public DateTime LastSaved { get; set; }
            public DateTime LastAccessed { get; set; }
            public TExtra Extra { get; set; }
        }

#if SILVERLIGHT
        private static EventsContext eventsContext;
        private static DispatcherTimer eventRequestTimer;
        private static DispatcherTimer eventDispatcher;
        private static Queue<EventDelegate> eventsQueue;

        public class RootsCollection : ObservableCollection<IRoot>, IRootsCollection
#else
        public class RootsCollection : List<IRoot>, IRootsCollection
#endif
        {
        } 

        public static IBuildProxy BuildProxy { get; set; }

#if SILVERLIGHT
        public static IHydraStatusBar StatusBar { get; set; }

#endif

        public static void SetStatus(string status)
        {
#if SILVERLIGHT

            if (StatusBar != null)
            {
                if (Log != null)
                {
                    // Log.Info(status);
                }

                StatusBar.Text = status;
            }
#endif
        }

        public static IUnityContainer Container
        {
            get
            {
                return IoC.Container;
            }
        }

        public static Dictionary<string, DomainContext> Contexts
        {
            get
            {
                return ProviderClientManager.contexts;
            }
        }

        public static List<ProviderListItem> Providers
        {
            get
            {
                if (providers == null)
                {
                    providers = new List<ProviderListItem>();
                }

                return providers;
            }
        }

        public static RootsCollection ProviderRoots
        {
            get
            {
                if (roots == null)
                {
                    roots = new RootsCollection();
                }

                return roots;
            }
        }

        public static void RaiseOnInternalError(Exception ex)
        {
            OnInternalError(ex);
        }

        public static void RaiseOnGeneralMessage(string message)
        {
            OnGeneralMessage(message);
        }

#if SILVERLIGHT
        public static ItemGenerationOperation<T> GenerateItemFromID<T>(string id) where T : IBase
        {
            var regex = new Regex(@"/\w+?\[@URL='(?<url>.*?)'\]");
            var itemGenerationOperation = new ItemGenerationOperation<T>();

            if (regex.IsMatch(id))
            {
                var match = regex.Match(id);
                var url = match.Groups["url"].Value;
                var timer = new DispatcherTimer();

                timer.Tick += (sender, e) =>
                {
                    var root = roots.SingleOrDefault(r => r.URL == url);

                    if (root != null)
                    {
                        var operation = root.GenerateItemFromID<T>(id);

                        operation.OnProgress += (sender2, e2) =>
                        {
                            itemGenerationOperation.SetProgress(e2.PercentComplete);
                        };

                        operation.Generated += (sender2, e2) =>
                        {
                            itemGenerationOperation.SetGenerated(sender2, e2.Item);
                        };

                        operation.ExceptionOccured += (sender2, e2) =>
                        {
                            itemGenerationOperation.SetException(sender2, e2.Exception);
                        };

                        timer.Stop();
                    }
                };

                timer.Interval = TimeSpan.FromMilliseconds(100);
                timer.Start();

                return itemGenerationOperation;
            }
            else
            {
                Debugger.Break();
                return null;
            }
        }
#endif

        public static void Initialize(ILog log, bool enableCaching = true)
        {
            contexts = new Dictionary<string, DomainContext>();
            loadRootMapping = new Dictionary<LoadOperation, Assembly>();

            roots = null;
            providers = null;
            providerQueue = null;

            ProviderClientManager.EnableCaching = enableCaching;

#if SILVERLIGHT
            var eventsStartOperation = StartEvents();

            eventsStartOperation.ExceptionOccured += (sender, e) =>
            {
                OnInternalError(e.Exception);
            };

            eventsStartOperation.OnEventsStarted += (sender, e) =>
            {
                SetStatus("Event service listener started");
            };

            ProviderClientManager.Log = log;

            LogManager.Error += (sender, e) =>
            {
                if (e.Exception != null)
                {
                    OnInternalError(e.Exception);
                    OnInternalMessage(e.Message);
                }
                else
                {
                    OnInternalMessage(e.Message);
                }
            };
#endif

            IoC.RegisterType<IProviderClientService, ProviderClientService>(new InjectionConstructor(log));
            IoC.RegisterType(typeof(IModelViewModel), typeof(ModelViewModel));
            IoC.RegisterType(typeof(IBindingsViewModel), typeof(BindingsViewModel));

            var client = new WebClient();

            /// TODO - (For SilverlightDatabase) here is where we will do the following:
            /// 
            /// 1. download a key from server
            /// 2. use that key to replace the extension .data below
            /// 3. request to download that file
            /// 4. the AbstraXProviderHandler via IHttpHandler will intercept and create a proprietary certificate with the following info:
            /// 
            ///     hash of the SF.dll
            ///     date and time UTC
            ///     user name
            ///     user guid key (database primary key)
            ///     certificate bytes (bytes used to open local SL database
            ///     
            ///     The cert bytes will be xor'd with the date and time as well as the SF.dll hash
            ///     This will produce the real password bytes
            ///     
            ///     Would be nice to use an obfuscation algorithm

            SetStatus("Loading user settings");

            client.OpenReadCompleted += (sender, e) =>
            {

            };

            client.OpenReadAsync(new Uri("e99FDs891c924e.data", UriKind.Relative));
        }

#if SILVERLIGHT
        private static void OnOpenException(OpenException exception, ref bool deleteAndRecreate)
        {
            if (MessageBox.Show("Client database is corrupted.  Would you like to recreate?", "Client database", MessageBoxButton.OKCancel) == MessageBoxResult.OK)
            {
                deleteAndRecreate = true;
            }
        }
#endif
        public static void CacheImage(string name, ImageDownloadType type, byte[] imageData)
        {

#if SILVERLIGHT
            if (ProviderClientManager.EnableCaching)
            {
                var savedFile = new SavedFile<LocalImage>
                {
                    FileName = name,
                    Extra = new LocalImage
                    {
                        DownloadType = type
                    },
                    Data = imageData,
                    LastSaved = DateTime.UtcNow
                };

                // Log.InfoFormat("Image '{0}' being added to database. Save date {1:MM/dd/yyyy hh:mm:ss}", name, savedFile.LastSaved);

                var task = new Task(() =>
                {
                    try
                    {
                        NameThread("CacheImage");

                        using (var session = DatabaseSession.Open(ProviderClientManager.DATABASE_NAME, ProviderClientManager.Log, OnOpenException))
                        {
                            var fileTable = session.GetTable<SavedFile<LocalImage>>();

                            if (fileTable.Any(s => s.FileName == name))
                            {
                                Debugger.Break();
                            }
                            else
                            {
                                fileTable.Add(savedFile);
                            }
                        }
                    }
                    catch (Exception ex)
                    {
                        throw;
                    }
                });

                task.Start();
            }
#endif
        }

        public static bool ImageInCache(string name, out byte[] imageData)
        {
            imageData = null;

#if SILVERLIGHT

            if (ProviderClientManager.EnableCaching)
            {
                try
                {
                    using (var session = DatabaseSession.Open(ProviderClientManager.DATABASE_NAME, ProviderClientManager.Log, OnOpenException))
                    {
                        var fileTable = session.GetTable<SavedFile<LocalImage>>();

                        if (fileTable.Any(s => s.FileName == name))
                        {
                            var image = fileTable.Single(s => s.FileName == name);

                            // Log.InfoFormat("Image '{0}' found in cache", name);

                            imageData = image.Data;

                            return true;
                        }
                        else
                        {
                            // Log.InfoFormat("Image '{0}' not found in cache. Loading.", name);

                            return false;
                        }
                    }
                }
                catch (Exception ex)
                {
                    throw;
                }
            }

#endif

            return false;
        }

        public static void LoadProviders(IEnumerable<ProviderListItem> providers)
        {
            providerQueue = new Queue<ProviderListItem>();

            SetStatus("Loading providers...");

#if SILVERLIGHT

            if (ProviderClientManager.EnableCaching)
            {
                try
                {
                    NameThread("LoadProviders");

                    foreach (var provider in providers)
                    {
                        using (var session = DatabaseSession.Open(ProviderClientManager.DATABASE_NAME, ProviderClientManager.Log, OnOpenException))
                        {
                            var fileTable = session.GetTable<SavedFile<ProviderListItemLocal>>();
                            var providersLeft = providers.Count();

                            if (fileTable.Any(s => s.FileName == provider.FileName))
                            {
                                OnGeneralMessage(string.Format("Provider '{0}' found in table.", provider.FileName));

                                var item = fileTable.Single(s => s.FileName == provider.FileName);
                                var providerListItem = (ProviderListItemLocal)item.Extra;
                                var modifiedDate = provider.ModifiedDate;

                                if (modifiedDate < item.LastSaved)
                                {
                                    var stream = item.Data.ToMemory();

                                    // Log.InfoFormat("Provider '{0}' server modified {1:MM/dd/yyyy hh:mm:ss} less than last saved {2:MM/dd/yyyy hh:mm:ss}.  Loading from cache.", provider.FileName, modifiedDate, item.LastSaved);

                                    provider.Cached = true;

                                    Providers.Add(provider);
                                    LoadAssembly(stream);
                                }
                                else
                                {
                                    // Log.InfoFormat("Provider '{0}' server modified {1:MM/dd/yyyy hh:mm:ss} greater than last saved {2:MM/dd/yyyy hh:mm:ss}.  Removing from database and adding to queue.", provider.FileName, modifiedDate, item.LastSaved);

                                    fileTable.Remove(item);

                                    providerQueue.Enqueue(provider);
                                    Providers.Add(provider);
                                }
                            }
                            else
                            {
                                // Log.InfoFormat("Provider '{0}' not found in database.  Adding to queue.", provider.FileName);

                                providerQueue.Enqueue(provider);
                                Providers.Add(provider);
                            }
                        }
                    }
                }
                catch (Exception ex)
                {
                    throw;
                }
            }
            else
            {
                foreach (var provider in providers)
                {
                    providerQueue.Enqueue(provider);
                    Providers.Add(provider);
                }
            }

#endif
            if (providerQueue.Count > 0)
            {
                var timer = new DispatcherTimer();

                timer.Interval = new TimeSpan(0, 0, 0, 0, 1);

                timer.Tick += (object s, EventArgs args) =>
                {
                    if (!loading)
                    {
                        var client = new WebClient();
                        var provider = providerQueue.Dequeue();

                        loading = true;

                        SetStatus("Loading " + Path.GetFileNameWithoutExtension(provider.FileName));

                        client.OpenReadCompleted += new OpenReadCompletedEventHandler(OnAssemblyOpened);
                        client.OpenReadAsync(new Uri(provider.FileName, UriKind.Relative));

                        if (providerQueue.Count == 0)
                        {
                            timer.Stop();
                        }
                    }
                };

                timer.Start();
            }
        }

        private static void OnAssemblyOpened(object sender, OpenReadCompletedEventArgs e)
        {
            var assemblyStream = e.Result.ToMemory();

            try
            {
                LoadAssembly(assemblyStream);
            }
            catch (Exception ex)
            {
                throw;
            }
        }

        private static void LoadAssembly(MemoryStream assemblyStream)
        {
            var rootType = string.Empty;
            var assemblyPart = new AssemblyPart();
            var loadStream = assemblyStream.ToMemory();
            var assembly = assemblyPart.Load(loadStream);
            var rootFound = false;
            var domainFound = false;
            var entityQueryFound = false;
            var name = assembly.ManifestModule.ToString();
            var item = providers.Single(p => p.FileName == name);

            foreach (Type type in assembly.GetTypes())
            {
                var interfaces = type.GetInterfaces().AsQueryable().Cast<Type>();

                if (interfaces.Any(t => t.Name == "IRoot") && !interfaces.Any(t => t.Name == "IRootSurrogate"))
                {
                    rootFound = true;
                    rootType = type.FullName.Replace(".Client.", ".");

                    foreach (Type type2 in assembly.GetTypes())
                    {
                        if (type2.BaseType != null && type2.BaseType.Name == "DomainContext" && type2.Name != "BindingsTreeContext" && type2.Name != "EventsContext" && type2.Name != "ProviderListContext")
                        {
                            DomainContext context;
                            var nameSpace = type.Namespace.Replace(".Client", "");

                            domainFound = true;

                            if (contexts.ContainsKey(nameSpace))
                            {
                                context = contexts[nameSpace];
                            }
                            else
                            {
                                context = Activator.CreateInstance(type2) as DomainContext;
                                contexts.Add(nameSpace, context);

                                var channelFactoryProperty = context.DomainClient.GetType().GetProperty("ChannelFactory");

                                if (channelFactoryProperty == null)
                                {
                                    throw new InvalidOperationException("There is no 'ChannelFactory' property on the DomainClient.");
                                }

                                var factory = (ChannelFactory)channelFactoryProperty.GetValue(context.DomainClient, null);

                                factory.Endpoint.Binding.SendTimeout = new TimeSpan(0, 5, 0);
                            }

                            foreach (MethodInfo methodInfo in type2.GetMethods())
                            {
                                if (methodInfo.ReturnType.Name == "EntityQuery`1")
                                {
                                    var typeParm = methodInfo.ReturnType.GetGenericArguments().AsQueryable().Cast<Type>().FirstOrDefault();

                                    if (typeParm.FullName == rootType)
                                    {
                                        var query = methodInfo.Invoke(context, new object[] { });
                                        var loadMethod = type2.GetMethods().AsQueryable().Cast<MethodInfo>().FirstOrDefault(m => m.Name == "Load");
                                        var loadGenericMethod = loadMethod.MakeGenericMethod(new Type[] { typeParm });
                                        var loadOp = (LoadOperation)loadGenericMethod.Invoke(context, new object[] { query });

                                        entityQueryFound = true;

                                        SetStatus("Loading roots for " + type.Name);

                                        loadRootMapping.Add(loadOp, assembly);

                                        loadOp.Completed += new EventHandler(LoadOperationCompleted);

                                        break;
                                    }
                                }
                            }
                        }
                    }

                    break;
                }
            }

            if (!rootFound)
            {
                // if you break here, compile everything in the AbstraXProviders folder first
                // if that doesn't work, the code generation is not projecting.

                Debugger.Break();
            }
            else if (!domainFound)
            {
                Debugger.Break();
            }
            else if (!entityQueryFound)
            {
                Debugger.Break();
            }

            if (ProviderClientManager.EnableCaching)
            {
                if (!item.Cached)
                {
                    CacheAssembly(assemblyStream, name, item);
                }
            }
        }

        private static void LoadOperationCompleted(object sender, EventArgs e)
        {
            var loadOp = (LoadOperation)sender;
            var entity = (Entity)loadOp.Entities.FirstOrDefault();
            var type = entity.GetType();
            var loadOperation = (LoadOperation)sender;
            var assembly = loadRootMapping[loadOperation];
            var clientType = assembly.GetType(type.Namespace + ".Client." + type.Name);

            loadRootMapping.Remove(loadOperation);

            var root = Activator.CreateInstance(clientType, new object[] { entity }) as IRoot;

            foreach (DomainContext context in ProviderClientManager.Contexts.Values)
            {
                if (context.GetType().Namespace + ".Entities.Client" == root.GetType().Namespace)
                {
#if SILVERLIGHT
                    root.Context = context;
#endif
                }
            }

            if (roots == null)
            {
                roots = new RootsCollection();
            }

            roots.Add(root);

            if (loadRootMapping.Count == 0)
            {
                SetStatus("All roots loaded.");

                RootsLoaded = true;

                if (OnRootsLoaded != null)
                {
                    OnRootsLoaded(loadRootMapping, new EventArgs<IRootsCollection>(roots));
                }
            }
            else
            {
                SetStatus("");
            }

            loading = false;
        }

        public static void NameThread(string name)
        {
            try
            {
                lock (namedThreads)
                {
                    var thread = Thread.CurrentThread;
                    var counter = 0;

                    if (string.IsNullOrEmpty(thread.Name))
                    {
                        namedThreads.Values.ToList().ForEach(t =>
                        {
                            if (t.IsAlive)
                            {
                                if (t.Name != null && t.Name.StartsWith(name))
                                {
                                    counter++;
                                }
                            }
                            else
                            {
                                namedThreads.Remove(t.Name);
                            }
                        });

                        try
                        {
                            namedThreads.Add(name, thread);
                        }
                        catch (Exception ex)
                        {

                        }

                        thread.Name = name + counter.ToString();
                    }
                }
            }
            catch (Exception ex)
            {

            }
        }

        private static void CacheAssembly(MemoryStream assemblyStream, string name, ProviderListItem item)
        {

#if SILVERLIGHT
            assemblyStream.Seek(0, SeekOrigin.Begin);

            var savedFile = new SavedFile<ProviderListItemLocal>
            {
                FileName = name,
                Extra = new ProviderListItemLocal
                {
                    FileName = item.FileName,
                    ModifiedDate = item.ModifiedDate,
                    ID = item.ID
                },
                Data = assemblyStream.ToArray(),
                LastSaved = DateTime.UtcNow
            };

            // Log.InfoFormat("Provider '{0}' being added to database. Save date {1:MM/dd/yyyy hh:mm:ss}", name, savedFile.LastSaved);

            assemblyStream.Close();

            var task = new Task(() =>
            {
                try
                {
                    NameThread("CacheAssembly");

                    using (var session = DatabaseSession.Open(ProviderClientManager.DATABASE_NAME, ProviderClientManager.Log, OnOpenException))
                    {
                        var fileTable = session.GetTable<SavedFile<ProviderListItemLocal>>();

                        if (fileTable.Any(s => s.FileName == savedFile.FileName))
                        {
                            Debugger.Break();
                        }
                        else
                        {
                            fileTable.Add(savedFile);
                        }
                    }

                }
                catch (Exception ex)
                {
                    throw;
                }
            });

            task.Start();
#endif
        }

        public static void AbortBuild(string lastUIComponentURL)
        {
            BuildProxy.AbortBuild(lastUIComponentURL);

#if SILVERLIGHT
            StopEvents();
#endif
            buildInvokeOperation.SetException(new Exception("Build aborted"));

            SetStatus("Build aborted");
        }

        public static BuildInvokeOperation BuildMiddleLayer(string id)
        {
            if (ProviderClientManager.BuildProxy == null)
            {
                ProviderClientManager.BuildProxy = ProviderClientManager.Container.Resolve<IBuildProxy>();
            }

            if (buildInvokeOperation != null)
            {
                if (!buildInvokeOperation.IsComplete)
                {
                    // TODO - future, handle multiple build requests

                    throw new Exception(string.Format("Illegal build request during a build started at {0}", buildInvokeOperation.BuildStarted));
                }
            }

            buildInvokeOperation = new BuildInvokeOperation();

            SetStatus("Requesting build.");

            BuildProxy.BuildMiddleLayer(id);

            return buildInvokeOperation;
        }

        public static bool BuildInProgress
        {
            get
            {
                return buildInvokeOperation != null && !buildInvokeOperation.IsComplete;
            }
        }

        public static void UnInitialize()
        {
        }

#if SILVERLIGHT

        public static void StopEvents()
        {
            if (eventRequestTimer != null)
            {
                eventRequestTimer.Stop();
            }

            if (eventDispatcher != null)
            {
                eventDispatcher.Stop();
            }

            while (eventsQueue.Count > 0)
            {
                var eventDelegate = eventsQueue.Dequeue();

                HandleEvent(eventDelegate);
            }
        }

        public static EventsStartOperation StartEvents()
        {
            var waiting = false;

            eventsQueue = new Queue<EventDelegate>();
            eventRequestTimer = new DispatcherTimer();
            eventDispatcher = new DispatcherTimer();
            eventsContext = new EventsContext();

            eventsStartOperation = new EventsStartOperation();

            SetStatus("Starting event service listener");

            eventRequestTimer.Tick += (sender2, e2) =>
            {
                if (!waiting) // Monitor.TryEnter not working
                {
                    waiting = true;

                    try
                    {
                        var operation = eventsContext.GetMessages();

                        operation.Completed += (sender3, e3) =>
                        {
                            try
                            {
                                var messages = operation.Value;

                                if (messages != null)
                                {
                                    messages.ToList().ForEach(m =>
                                    {
                                        switch (m.Message)
                                        {
                                            case Message.SolutionOpened:
                                                {
                                                    if (OnSolutionOpened != null)
                                                    {
                                                        var parms = m.ReadFrom();
                                                        var solutionName = (string)parms[0];

                                                        eventsQueue.Enqueue(new EventDelegate1<OnSolutionOpenedHandler, string>(m, OnSolutionOpened, solutionName));
                                                    }

                                                    break;
                                                }
                                            case Message.ShowUI:
                                                {
                                                    if (OnShowUI != null)
                                                    {
                                                        var parms = m.ReadFrom();
                                                        var uiComponent = (string)parms[0];
                                                        var parms2 = (string)parms[1];

                                                        eventsQueue.Enqueue(new EventDelegate2<OnShowUIHandler, string, string>(m, OnShowUI, uiComponent, parms2));
                                                    }

                                                    break;
                                                }
                                            case Message.GeneralMessage:
                                                {
                                                    if (OnGeneralMessage != null)
                                                    {
                                                        var parms = m.ReadFrom();
                                                        var message = (string)parms[0];

                                                        eventsQueue.Enqueue(new EventDelegate1<OnGeneralMessageHandler, string>(m, OnGeneralMessage, message));
                                                    }

                                                    break;
                                                }
                                            case Message.PercentComplete:
                                                {
                                                    if (OnPercentComplete != null)
                                                    {
                                                        var parms = m.ReadFrom();
                                                        var percentComplete = (float)parms[0];

                                                        eventsQueue.Enqueue(new EventDelegate1<OnPercentCompleteHandler, float>(m, OnPercentComplete, percentComplete));
                                                    }

                                                    break;
                                                }
                                            case Message.GeneralError:
                                                {
                                                    if (OnGeneralError != null)
                                                    {
                                                        var parms = m.ReadFrom();
                                                        var generalError = (RemotableException)parms[0];

                                                        eventsQueue.Enqueue(new EventDelegate1<OnGeneralErrorHandler, RemotableException>(m, OnGeneralError, generalError));
                                                    }

                                                    break;
                                                }
                                            case Message.FatalError:
                                                {
                                                    if (OnFatalError != null)
                                                    {
                                                        var parms = m.ReadFrom();
                                                        var fatalError = (RemotableException)parms[0];

                                                        eventsQueue.Enqueue(new EventDelegate1<OnFatalErrorHandler, RemotableException>(m, OnFatalError, fatalError));
                                                    }

                                                    break;
                                                }
                                            case Message.GenerationCompleted:
                                                {
                                                    if (OnGenerationCompleted != null)
                                                    {
                                                        var parms = m.ReadFrom();

                                                        eventsQueue.Enqueue(new EventDelegate0<OnGenerationCompletedHandler>(m, OnGenerationCompleted));
                                                    }

                                                    break;
                                                }
                                            case Message.RequestingBuild:
                                                {
                                                    if (OnBuildRequested != null)
                                                    {
                                                        var parms = m.ReadFrom();
                                                        var generatorName = (string)parms[0];

                                                        eventsQueue.Enqueue(new EventDelegate1<OnBuildRequestedHandler, string>(m, OnBuildRequested, generatorName));
                                                    }

                                                    break;
                                                }
                                            case Message.BuildRequestCompleted:
                                                {
                                                    if (OnBuildRequestCompleted != null)
                                                    {
                                                        var parms = m.ReadFrom();
                                                        var generatorName = (string)parms[0];

                                                        eventsQueue.Enqueue(new EventDelegate1<OnBuildRequestCompletedHandler, string>(m, OnBuildRequestCompleted, generatorName));
                                                    }

                                                    break;
                                                }
                                            case Message.BuildFinished:
                                                {
                                                    if (OnBuildFinished != null)
                                                    {
                                                        var parms = m.ReadFrom();
                                                        var args = (BuildFinishedEventArgs)parms[0];

                                                        eventsQueue.Enqueue(new EventDelegate1<OnBuildFinishedHandler, BuildFinishedEventArgs>(m, OnBuildFinished, args));
                                                    }

                                                    break;
                                                }
                                            case Message.BuildStarted:
                                                {
                                                    if (OnBuildStarted != null)
                                                    {
                                                        var parms = m.ReadFrom();
                                                        var args = (BuildStartedEventArgs)parms[0];

                                                        eventsQueue.Enqueue(new EventDelegate1<OnBuildStartedHandler, BuildStartedEventArgs>(m, OnBuildStarted, args));
                                                    }

                                                    break;
                                                }
                                            case Message.CustomEventRaised:
                                                {
                                                    if (OnCustomEventRaised != null)
                                                    {
                                                        var parms = m.ReadFrom();
                                                        var args = (CustomBuildEventArgs)parms[0];

                                                        eventsQueue.Enqueue(new EventDelegate1<OnCustomEventRaisedHandler, CustomBuildEventArgs>(m, OnCustomEventRaised, args));
                                                    }

                                                    break;
                                                }
                                            case Message.ErrorRaised:
                                                {
                                                    if (OnErrorRaised != null)
                                                    {
                                                        var parms = m.ReadFrom();
                                                        var args = (BuildErrorEventArgs)parms[0];

                                                        eventsQueue.Enqueue(new EventDelegate1<OnErrorRaisedHandler, BuildErrorEventArgs>(m, OnErrorRaised, args));
                                                    }

                                                    break;
                                                }
                                            case Message.MessageRaised:
                                                {
                                                    if (OnMessageRaised != null)
                                                    {
                                                        var parms = m.ReadFrom();
                                                        var args = (BuildMessageEventArgs)parms[0];

                                                        eventsQueue.Enqueue(new EventDelegate1<OnMessageRaisedHandler, BuildMessageEventArgs>(m, OnMessageRaised, args));
                                                    }

                                                    break;
                                                }
                                            case Message.ProjectFinished:
                                                {
                                                    if (OnProjectFinished != null)
                                                    {
                                                        var parms = m.ReadFrom();
                                                        var args = (ProjectFinishedEventArgs)parms[0];

                                                        eventsQueue.Enqueue(new EventDelegate1<OnProjectFinishedHandler, ProjectFinishedEventArgs>(m, OnProjectFinished, args));
                                                    }

                                                    break;
                                                }
                                            case Message.ProjectStarted:
                                                {
                                                    if (OnProjectStarted != null)
                                                    {
                                                        var parms = m.ReadFrom();
                                                        var args = (ProjectStartedEventArgs)parms[0];

                                                        eventsQueue.Enqueue(new EventDelegate1<OnProjectStartedHandler, ProjectStartedEventArgs>(m, OnProjectStarted, args));
                                                    }

                                                    break;
                                                }
                                            case Message.StatusEventRaised:
                                                {
                                                    if (OnStatusEventRaised != null)
                                                    {
                                                        var parms = m.ReadFrom();
                                                        var args = (BuildStatusEventArgs)parms[0];

                                                        eventsQueue.Enqueue(new EventDelegate1<OnStatusEventRaisedHandler, BuildStatusEventArgs>(m, OnStatusEventRaised, args));
                                                    }

                                                    break;
                                                }
                                            case Message.TargetFinished:
                                                {
                                                    if (OnTargetFinished != null)
                                                    {
                                                        var parms = m.ReadFrom();
                                                        var args = (TargetFinishedEventArgs)parms[0];

                                                        eventsQueue.Enqueue(new EventDelegate1<OnTargetFinishedHandler, TargetFinishedEventArgs>(m, OnTargetFinished, args));
                                                    }

                                                    break;
                                                }
                                            case Message.TargetStarted:
                                                {
                                                    if (OnTargetStarted != null)
                                                    {
                                                        var parms = m.ReadFrom();
                                                        var args = (TargetStartedEventArgs)parms[0];

                                                        eventsQueue.Enqueue(new EventDelegate1<OnTargetStartedHandler, TargetStartedEventArgs>(m, OnTargetStarted, args));
                                                    }

                                                    break;
                                                }
                                            case Message.TaskFinished:
                                                {
                                                    if (OnTaskFinished != null)
                                                    {
                                                        var parms = m.ReadFrom();
                                                        var args = (TaskFinishedEventArgs)parms[0];

                                                        eventsQueue.Enqueue(new EventDelegate1<OnTaskFinishedHandler, TaskFinishedEventArgs>(m, OnTaskFinished, args));
                                                    }

                                                    break;
                                                }
                                            case Message.TaskStarted:
                                                {
                                                    if (OnTaskStarted != null)
                                                    {
                                                        var parms = m.ReadFrom();
                                                        var args = (TaskStartedEventArgs)parms[0];

                                                        eventsQueue.Enqueue(new EventDelegate1<OnTaskStartedHandler, TaskStartedEventArgs>(m, OnTaskStarted, args));
                                                    }

                                                    break;
                                                }
                                            case Message.WarningRaised:
                                                {
                                                    if (OnWarningRaised != null)
                                                    {
                                                        var parms = m.ReadFrom();
                                                        var args = (BuildWarningEventArgs)parms[0];

                                                        eventsQueue.Enqueue(new EventDelegate1<OnWarningRaisedHandler, BuildWarningEventArgs>(m, OnWarningRaised, args));
                                                    }

                                                    break;
                                                }
                                        }
                                    });
                                }

                                if (eventsStartOperation != null)
                                {
                                    eventsStartOperation.SetCompleted();
                                    eventsStartOperation = null;
                                }
                            }
                            catch (Exception ex)
                            {
                                Debugger.Break();
                            }

                            waiting = false;
                        };
                    }
                    catch (Exception ex)
                    {
                        Debugger.Break();
                    }
                }
            };

            var inProcess = false;
            var lastTimeStamp = DateTime.Now;

            eventDispatcher.Tick += (sender2, e2) =>
            {
                if (!inProcess)
                {
                    inProcess = true;

                    if (eventsQueue.Count > 0)
                    {
                        var eventDelegate = eventsQueue.Dequeue();

                        if (eventDelegate.HasArgs)
                        {
                            var args = eventDelegate.BuildEventArgs;
                            var time = new DateTime(args.Timestamp.Ticks);
                            var lastTime = new DateTime(lastTimeStamp.Ticks);

                            var diff = (time - lastTime).Milliseconds;

                            Thread.Sleep(diff);

                            lastTimeStamp = args.Timestamp;
                        }

                        HandleEvent(eventDelegate);
                    }

                    inProcess = false;
                }
            };

            eventDispatcher.Interval = new TimeSpan(0, 0, 0, 0, 0); // one millisecond
            eventDispatcher.Start();

            eventRequestTimer.Interval = new TimeSpan(0, 0, 0, 0, 1000); // one second
            eventRequestTimer.Start();

            return eventsStartOperation;
        }

        private static void HandleEvent(EventDelegate eventDelegate)
        {
            switch (eventDelegate.EventMessage.Message)
            {
                case Message.SolutionOpened:
                    {
                        if (OnSolutionOpened != null)
                        {
                            var eventDelegateX = (EventDelegate1<OnSolutionOpenedHandler, string>)eventDelegate;

                            eventDelegateX.EventDelegate(eventDelegateX.Parm1);
                        }

                        break;
                    }
                case Message.ShowUI:
                    {
                        if (OnShowUI != null)
                        {
                            var eventDelegateX = (EventDelegate2<OnShowUIHandler, string, string>)eventDelegate;

                            eventDelegateX.EventDelegate(eventDelegateX.Parm1, eventDelegateX.Parm2);
                        }

                        break;
                    }
                case Message.GeneralMessage:
                    {
                        if (OnGeneralMessage != null)
                        {
                            var eventDelegateX = (EventDelegate1<OnGeneralMessageHandler, string>)eventDelegate;

                            eventDelegateX.EventDelegate(eventDelegateX.Parm1);
                        }

                        break;
                    }
                case Message.PercentComplete:
                    {
                        if (OnPercentComplete != null)
                        {
                            var eventDelegateX = (EventDelegate1<OnPercentCompleteHandler, float>)eventDelegate;

                            eventDelegateX.EventDelegate(eventDelegateX.Parm1);
                        }

                        break;
                    }
                case Message.GeneralError:
                    {
                        if (OnGeneralError != null)
                        {
                            var eventDelegateX = (EventDelegate1<OnGeneralErrorHandler, RemotableException>)eventDelegate;

                            eventDelegateX.EventDelegate(eventDelegateX.Parm1);
                        }

                        break;
                    }
                case Message.FatalError:
                    {
                        if (OnFatalError != null)
                        {
                            var eventDelegateX = (EventDelegate1<OnFatalErrorHandler, RemotableException>)eventDelegate;

                            eventDelegateX.EventDelegate(eventDelegateX.Parm1);
                        }

                        break;
                    }
                case Message.GenerationCompleted:
                    {
                        if (OnGenerationCompleted != null)
                        {
                            var eventDelegateX = (EventDelegate0<OnGenerationCompletedHandler>)eventDelegate;

                            eventDelegateX.EventDelegate();
                        }

                        break;
                    }
                case Message.RequestingBuild:
                    {
                        if (OnBuildRequested != null)
                        {
                            var eventDelegateX = (EventDelegate1<OnBuildRequestedHandler, string>)eventDelegate;

                            eventDelegateX.EventDelegate(eventDelegateX.Parm1);
                        }

                        break;
                    }
                case Message.BuildRequestCompleted:
                    {
                        if (OnBuildRequestCompleted != null)
                        {
                            var eventDelegateX = (EventDelegate1<OnBuildRequestCompletedHandler, string>)eventDelegate;

                            eventDelegateX.EventDelegate(eventDelegateX.Parm1);
                        }

                        break;
                    }
                case Message.BuildFinished:
                    {
                        if (OnBuildFinished != null)
                        {
                            var eventDelegateX = (EventDelegate1<OnBuildFinishedHandler, BuildFinishedEventArgs>)eventDelegate;

                            eventDelegateX.EventDelegate(eventDelegateX.Parm1);
                        }

                        break;
                    }
                case Message.BuildStarted:
                    {
                        if (OnBuildStarted != null)
                        {
                            var eventDelegateX = (EventDelegate1<OnBuildStartedHandler, BuildStartedEventArgs>)eventDelegate;

                            eventDelegateX.EventDelegate(eventDelegateX.Parm1);
                        }

                        break;
                    }
                case Message.CustomEventRaised:
                    {
                        if (OnCustomEventRaised != null)
                        {
                            var eventDelegateX = (EventDelegate1<OnCustomEventRaisedHandler, CustomBuildEventArgs>)eventDelegate;

                            eventDelegateX.EventDelegate(eventDelegateX.Parm1);
                        }

                        break;
                    }
                case Message.ErrorRaised:
                    {
                        if (OnErrorRaised != null)
                        {
                            var eventDelegateX = (EventDelegate1<OnErrorRaisedHandler, BuildErrorEventArgs>)eventDelegate;

                            eventDelegateX.EventDelegate(eventDelegateX.Parm1);
                        }

                        break;
                    }
                case Message.MessageRaised:
                    {
                        if (OnMessageRaised != null)
                        {
                            var eventDelegateX = (EventDelegate1<OnMessageRaisedHandler, BuildMessageEventArgs>)eventDelegate;

                            eventDelegateX.EventDelegate(eventDelegateX.Parm1);
                        }

                        break;
                    }
                case Message.ProjectFinished:
                    {
                        if (OnProjectFinished != null)
                        {
                            var eventDelegateX = (EventDelegate1<OnProjectFinishedHandler, ProjectFinishedEventArgs>)eventDelegate;

                            eventDelegateX.EventDelegate(eventDelegateX.Parm1);
                        }

                        break;
                    }
                case Message.ProjectStarted:
                    {
                        if (OnProjectStarted != null)
                        {
                            var eventDelegateX = (EventDelegate1<OnProjectStartedHandler, ProjectStartedEventArgs>)eventDelegate;

                            eventDelegateX.EventDelegate(eventDelegateX.Parm1);
                        }

                        break;
                    }
                case Message.StatusEventRaised:
                    {
                        if (OnStatusEventRaised != null)
                        {
                            var eventDelegateX = (EventDelegate1<OnStatusEventRaisedHandler, BuildStatusEventArgs>)eventDelegate;

                            eventDelegateX.EventDelegate(eventDelegateX.Parm1);
                        }

                        break;
                    }
                case Message.TargetFinished:
                    {
                        if (OnTargetFinished != null)
                        {
                            var eventDelegateX = (EventDelegate1<OnTargetFinishedHandler, TargetFinishedEventArgs>)eventDelegate;

                            eventDelegateX.EventDelegate(eventDelegateX.Parm1);
                        }

                        break;
                    }
                case Message.TargetStarted:
                    {
                        if (OnTargetStarted != null)
                        {
                            var eventDelegateX = (EventDelegate1<OnTargetStartedHandler, TargetStartedEventArgs>)eventDelegate;

                            eventDelegateX.EventDelegate(eventDelegateX.Parm1);
                        }

                        break;
                    }
                case Message.TaskFinished:
                    {
                        if (OnTaskFinished != null)
                        {
                            var eventDelegateX = (EventDelegate1<OnTaskFinishedHandler, TaskFinishedEventArgs>)eventDelegate;

                            eventDelegateX.EventDelegate(eventDelegateX.Parm1);
                        }

                        break;
                    }
                case Message.TaskStarted:
                    {
                        if (OnTaskStarted != null)
                        {
                            var eventDelegateX = (EventDelegate1<OnTaskStartedHandler, TaskStartedEventArgs>)eventDelegate;

                            eventDelegateX.EventDelegate(eventDelegateX.Parm1);
                        }

                        break;
                    }
                case Message.WarningRaised:
                    {
                        if (OnWarningRaised != null)
                        {
                            var eventDelegateX = (EventDelegate1<OnWarningRaisedHandler, BuildWarningEventArgs>)eventDelegate;

                            eventDelegateX.EventDelegate(eventDelegateX.Parm1);
                        }

                        break;
                    }
                default:
                    Debugger.Break();
                    break;
            }
        }
#endif
    }

    public abstract class EventDelegate
    {
        public BuildEventArgs BuildEventArgs { get; protected set; }
        public Delegate BaseDelegate { get; protected set; }
        public AbstraX.EventMessage EventMessage { get; protected set; }

        public bool HasArgs
        {
            get
            {
                return this.BuildEventArgs != null;
            }
        }

        public EventDelegate(AbstraX.EventMessage message, Delegate baseDelegate)
        {
            this.BaseDelegate = baseDelegate;
            this.EventMessage = message;
        }
    }

    public interface IEventDelegate0<out TDelegate>
    {
        TDelegate EventDelegate { get; }
    }

    public class EventDelegate0<TDelegate> : EventDelegate, IEventDelegate0<TDelegate>
    {
        public TDelegate EventDelegate { get; private set; }

        public EventDelegate0(AbstraX.EventMessage eventMessage, TDelegate eventDelegate)
            : base(eventMessage, (Delegate)(object)eventDelegate)
        {
            this.EventDelegate = eventDelegate;
        }
    }

    public interface IEventDelegate1<out TDelegate, out TParm1>
    {
        TDelegate EventDelegate { get; }
        TParm1 Parm1 { get; }
    }

    public class EventDelegate1<TDelegate, TParm1> : EventDelegate, IEventDelegate1<TDelegate, TParm1>
    {
        public TDelegate EventDelegate { get; private set; }
        public TParm1 Parm1 { get; private set; }

        public EventDelegate1(AbstraX.EventMessage eventMessage, TDelegate eventDelegate, TParm1 parm1)
            : base(eventMessage, (Delegate)(object)eventDelegate)
        {
            var type = typeof(TParm1);

            this.EventMessage = eventMessage;
            this.EventDelegate = eventDelegate;
            this.Parm1 = parm1;
            Func<Type, string, bool> isType = null;

            isType = (type2, name) =>
            {
                if (type2.Name == name)
                {
                    return true;
                }
                else if (type2.BaseType != null)
                {
                    var b = isType(type2.BaseType, name);

                    return b;
                }

                return false;
            };

            if (isType(type, "BuildEventArgs"))
            {
                this.BuildEventArgs = (BuildEventArgs)(object)parm1;
            }
        }
    }

    public interface IEventDelegate2<out TDelegate, out TParm1, out TParm2>
    {
        TDelegate EventDelegate { get; }
        TParm1 Parm1 { get; }
        TParm2 Parm2 { get; }
    }

    public class EventDelegate2<TDelegate, TParm1, TParm2> : EventDelegate, IEventDelegate2<TDelegate, TParm1, TParm2>
    {
        public TDelegate EventDelegate { get; private set; }
        public TParm1 Parm1 { get; private set; }
        public TParm2 Parm2 { get; private set; }

        public EventDelegate2(AbstraX.EventMessage eventMessage, TDelegate eventDelegate, TParm1 parm1, TParm2 parm2) : base(eventMessage, (Delegate)(object)eventDelegate)
        {
            var type = typeof(TParm2);

            this.EventMessage = eventMessage;
            this.EventDelegate = eventDelegate;
            this.Parm1 = parm1;
            this.Parm2 = parm2;
            Func<Type, string, bool> isType = null;

            isType = (type2, name) =>
            {
                if (type2.Name == name)
                {
                    return true;
                }
                else if (type2.BaseType != null)
                {
                    var b = isType(type2.BaseType, name);

                    return b;
                }

                return false;
            };

            if (isType(type, "BuildEventArgs"))
            {
                this.BuildEventArgs = (BuildEventArgs)(object)parm1;
            }
        }
    }
}

//#endif